﻿package net.easymvc.core;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import net.easymvc.annotation.Encoding;
import net.easymvc.annotation.FilePath;
import net.easymvc.annotation.FormData;
import net.easymvc.annotation.Param;
import net.easymvc.annotation.Valid;

public class CoreServlet extends HttpServlet {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -5518076621670974238L;
	
	//文件分隔符
	public static final String FILE_SEPARATOR = System.getProperties().getProperty("file.separator");
	private String packageName;
//	private String errorPage;
	private ThreadLocal<Boolean> isGet = new ThreadLocal<>();
	
	
	public void init(ServletConfig config) throws ServletException {
		packageName = config.getInitParameter("package");
//		errorPage = config.getInitParameter("error");
	}

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		isGet.set(true);
		doPost(request, response);
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		if(isGet.get()==null)isGet.set(false);
		String path = request.getContextPath();
		String basePath = request.getScheme() + "://" + request.getServerName()
				+ ":" + request.getServerPort() + path + FILE_SEPARATOR;
		request.setAttribute("basePath", basePath);

		String url = request.getRequestURI();
		if(url.equals(request.getContextPath() + "/")){
			url += request.getServletPath();
		}
		String[] ss = url.split("/");
		String ActionName = packageName + "."
				+ ss[ss.length - 2].substring(0, 1).toUpperCase()
				+ ss[ss.length - 2].substring(1) + "Action";
		String MethodName = ss[ss.length - 1].substring(0,
				ss[ss.length - 1].indexOf("."));
		try {
			// 加载控制器
			Class<?> ActionClass = Class.forName(ActionName);
			Action action = (Action) ActionClass.newInstance();
			// 设置request,response,session
			action.setRequest(request);
			action.setResponse(response);
			action.setSession(request.getSession());
			// 查找请求方法
			Method reqMethod = getRequestMethod(MethodName, ActionClass);
			
			Encoding encoding = reqMethod.getAnnotation(Encoding.class);
			String encodingStr = null;
			if(encoding!=null){
				if(isGet.get()){
					encodingStr = encoding.value();
				}else{
					request.setCharacterEncoding(encoding.value());
				}
			}
			response.setContentType("text/html;charset=utf-8");
			response.setCharacterEncoding(encodingStr);
			// 获得参数类型
			Class<?>[] paramTypes = reqMethod.getParameterTypes();
			String toUrl=null;
			//上传文件
			if(reqMethod.isAnnotationPresent(FormData.class)){
				//上传文件时form表单的内容不同的填充方式
				toUrl = upload(request, response,action,reqMethod,paramTypes, encodingStr);
			}else{
				//不上传时使用另外一种方法
				System.out.println("执行方法:"+reqMethod);
				toUrl = methodInvoke(request,action,reqMethod,paramTypes,encodingStr,null,null);
			}
			if(toUrl == null) return;
			// 转发网页
			if (toUrl.startsWith("redirect")) {
				toUrl = toUrl.split("[:]")[1];
				response.sendRedirect(basePath+toUrl);
			}else if(toUrl.startsWith("ajax")){
				toUrl = toUrl.substring("ajax:".length());
				response.getWriter().write(toUrl);
				response.getWriter().flush();
				response.getWriter().close();
			}else {
				request.getRequestDispatcher(toUrl).forward(request, response);
			}
			isGet.set(null);
		} catch (Exception e) {
			e.printStackTrace();
			try {
				checkException(e.getClass().getName(),e.getMessage());
			} catch (Exception e1) {
				e1.printStackTrace();
			}
			
//			request.setAttribute("err", e.getMessage());
//			request.getRequestDispatcher(errorPage).forward(request, response);
		}

	}
	private String methodInvoke(HttpServletRequest request,Action action, Method reqMethod,  Class<?>[] paramTypes,
			String encoding, Map<String,String> formData, String filePath) throws Exception{
		if(paramTypes.length>0){
			// 获得参数注解
			Annotation[][] as = reqMethod.getParameterAnnotations();
			Object[] params=null;
			// 验证数据
			//TODO 验证数据时也取出数据，较为浪费
			if(validate(request,paramTypes,as, encoding, formData)){
				// 填充参数
				params = populateParams(request, paramTypes,as, encoding, formData, filePath);
			}else{
				// 验证未通过,参数为空
				params=new Object[paramTypes.length];
				action.setValidate(false);
			}
			// 执行请求方法
			return (String) reqMethod.invoke(action, params);
		}else{
			return (String) reqMethod.invoke(action);
		}
	}
	
	private String upload(HttpServletRequest request, HttpServletResponse response, Action action, 
			Method reqMethod, Class<?>[] paramTypes, String encoding){
		// 创建文件工厂对象
		DiskFileItemFactory factory = new DiskFileItemFactory();
		// 将文件保存在内存还是磁盘临时文件夹的临界值
		factory.setSizeThreshold(1024 * 200);
		String path = request.getSession().getServletContext().getRealPath("")
				+ FILE_SEPARATOR + "upload" + FILE_SEPARATOR;
		File tempDirectory = new File(path + "cache" + FILE_SEPARATOR);
		factory.setRepository(tempDirectory);
		// 文件上传类
		ServletFileUpload upload = new ServletFileUpload(factory);
		if(encoding!=null)upload.setHeaderEncoding(encoding);
		// 设置上传的文件大小临界值，超出会报错
		upload.setSizeMax(1024 * 1024 * 5);
		String fileName = null;
		String name = null;
		String value = null;
		String filePath = null;
		String downloadPath = null;
		try{
			// 接收用户上传信息
			List<FileItem> items = upload.parseRequest(request);
			if(items == null){
				request.setAttribute("error_msg", "当前无文件");
				return null;
			}
			Map<String,String> formData = new HashMap<String, String>();
			// 遍历items
			for (FileItem item : items) {
				// 一般表单域
				if (item.isFormField()) {
					name = item.getFieldName();
					if(encoding!=null)value = item.getString(encoding);
					else value = item.getString();
					formData.put(name, value);
					System.out.println("name="+name+",value="+value);
				}
				// 若是文件域则把文件保存到临时文件夹
				else {
					// 上传的文件名
					fileName = item.getName();
					// 获取文件后缀名并转化为写，用于后续比较
					String fileType = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()).toLowerCase();
					//判断文件类型
					if(!fileTypeCheck(fileType)){
						//TODO LOGGER
						System.out.println("错误文件类型="+fileType);
						request.setAttribute("error_msg", "文件类型错误，无法读取" + fileType + "类型");
						return null;
					}
					String randomName = System.currentTimeMillis() + "." + fileType;
					filePath = path + randomName;
					File file = new File(filePath);
					downloadPath = "upload.dl?fileName=" + randomName;
					item.write(file);
					item.delete();
				}
			}
			return methodInvoke(request,action,reqMethod,paramTypes,encoding,formData, downloadPath);
		}catch(Exception e){
			e.printStackTrace();
			request.setAttribute("error_msg", "文件上传失败 " + e.getMessage());
			return null;
		}
	}
	
	private boolean fileTypeCheck(String fileType){
		// 创建压缩类型数组
		String[] compressed = { "rar", "zip", "7z", "bz", "ace", "gz", "arj", "uha", "uda", "jar", "zpaq", "z",
				"lzh", "pcd", "dxf", "ufo", "eps", "ai", "raw", "wmf" };
		if(loopCheck(compressed,fileType))return true;
		// 创建图片类型数组
		String[] img = { "bmp", "jpg", "jpeg", "png", "tiff", "gif", "pcx", "tga", "exif", "fpx", "svg", "psd",
		"cdr", "pcd", "dxf", "ufo", "eps", "ai", "raw", "wmf"};
		if(loopCheck(img,fileType))return true;
		// 创建文档类型数组
		String[] document = { "txt", "doc", "docx", "xls", "xlsx", "htm", "html", "jsp", "rtf", "wpd", "wps", 
				"pdf", "ppt" , "pptx", "csv" };
		if(loopCheck(document,fileType))return true;
		// 创建视频类型数组
		String[] video = { "mp4", "avi", "mov", "wmv", "asf", "navi", "3gp", "mkv", "f4v", "rmvb", "webm" };
		if(loopCheck(video,fileType))return true;
		// 创建音乐类型数组
		String[] music = { "mp3", "wma", "wav", "mod", "ra", "cd", "md", "asf", "aac", "vqf", "ape", "mid", "ogg",
		"m4a", "vqf" };
		if(loopCheck(music,fileType))return true;
		return false;
	}
	
	private boolean loopCheck(String[] strs, String fileType){
		for (int i = 0; i < strs.length; i++) {
			if (strs[i].equals(fileType)) {
			return true;
			}
		}
		return false;
	}
	
	private void checkException(String exception,String message) throws Exception {
		//TODO
		if(exception.contains("ClassNotFoundException")){
			throw new ClassNotFoundException("该类不存在，请检查"+message+"类名是否正确");
		}else if(exception.contains("ParseException")){
			throw new ParseException("不可解析的日期"+message.substring(17)+"，请检查日期格式是否正确", 0);
		}else if(exception.contains("UnsupportedEncodingException")){
			throw new UnsupportedEncodingException("错误的编码"+message+"，请检查编码代号是否正确");
		}else if(exception.contains("NumberFormatException")){
			throw new NumberFormatException("无法将"+message.substring(18)+"转为数字类型");
		}else if(exception.contains("InstantiationException")){
			throw new InstantiationException("无法将"+message+"实例化，请检查该类是否为可实例化类"
					+ "\n\t接口、注释、抽象类等不可实例化或类中没有无参构造器");
		}else if(exception.contains("IllegalAccessException")){
			String str1 = "can not access a member of class ";
			String str2 = " with modifiers \"private\"";
			throw new IllegalAccessException("无法访问"+message.substring(message.indexOf(str1)+str1.length(),message.indexOf(str2))
					+"类中修饰符为private的成员或不同包中的protected的成员 \n\t"+message);
		}
	}

	/**
	 * 验证上传数据
	 * @param request
	 * @param paramTypes
	 * @param paramAnnotations
	 * @param encoding
	 * @return
	 * @throws UnsupportedEncodingException 
	 */
	private boolean validate(HttpServletRequest request, Class<?>[] paramTypes,	Annotation[][] paramAnnotations,
			String encoding,Map<String,String> formData) throws Exception {
		//保存回显数据
		Map<String,String[]> map=request.getParameterMap();
		for(String name:map.keySet()){
			if(encoding!=null){
				map.get(name)[0] = new String(map.get(name)[0].getBytes(),encoding);
			}
			request.setAttribute(name, map.get(name)[0]);
		}
		boolean r=true;
		for (int i = 0; i < paramAnnotations.length; i++) {
			if (paramAnnotations[i].length == 0) {
				r=validBean(request, paramTypes[i], formData, encoding);
				if(!r)break;
			} else {
				Annotation a = paramAnnotations[i][0];
				if (a instanceof Param) {
					Param pa = (Param) a;
					String paramName = pa.value();
					String pattern = pa.pattern();
					String message = pa.message();
					if(formData == null) r = validParam(request, paramTypes[i], paramName, pattern, null, encoding);
					else{
						for (String key : formData.keySet()) {
							if(paramName.equals(key)) r = validParam(request, paramTypes[i], paramName, pattern, formData.get(key), encoding);
						}
					}
					if(!r){
						request.setAttribute("_valid_message_", message);
						break;
					}
				}
			}
		}
		return r;
		
	}
	
	/**
	 * 验证实体参数
	 * @param request
	 * @param type
	 * @param encoding
	 * @return
	 * @throws UnsupportedEncodingException 
	 */
	private boolean validBean(HttpServletRequest request, Class<?> type, Map<String,String> formData, String encoding) throws Exception {
		boolean r=true;
		Field[] flds = type.getDeclaredFields();
		for (int i = 0; i < flds.length; i++) {
			String paramName = flds[i].getName();
			Valid va=flds[i].getAnnotation(Valid.class);
			if(va!=null){
				String pattern = va.pattern();
				String message = va.message();
				if(formData == null) r = validParam(request, flds[i].getType(), paramName, pattern, null, encoding);
				else{
					for (String key : formData.keySet()) {
						if(paramName.equals(key)) r = validParam(request, flds[i].getType(), paramName, pattern, formData.get(key), encoding);
					}
				}
				if(!r){
					request.setAttribute("_valid_message_", message);
					break;
				}				
			}
		}
		return r;
	}

	/**
	 * 验证参数
	 * @param request
	 * @param paramType
	 * @param paramName
	 * @param pattern
	 * @return
	 * @throws UnsupportedEncodingException 
	 */
	private boolean validParam(HttpServletRequest request,Class<?> paramType, String paramName,
			String pattern, String formValue, String encoding) throws Exception {
		boolean r=true;
		if(pattern!=null && pattern.length()>0){
			String value = null;
			//如果是form-data的表单元素则直接获得值
			if(formValue==null)value=request.getParameter(paramName);
			else value = formValue;
			if(encoding!=null)value = new String(value.getBytes("ISO-8859-1"),encoding);
			if(paramType.getName().contains("Date")){
				SimpleDateFormat sf=new SimpleDateFormat(pattern);
				try {
					sf.parse(value);
				} catch (ParseException e) {
					r=false;
					throw new ParseException("不可解析的日期"+e.getMessage()+"，请检查日期格式是否正确",0);
				}
			}else{
				if(!Pattern.matches(pattern, value)){
					r=false;
				}
			}
		}
		return r;
	}
	/**
	 * 自动封装参数
	 * @param request
	 * @param paramTypes
	 * @param paramAnnotations
	 * @return
	 * @throws Exception
	 */
	private Object[] populateParams(HttpServletRequest request, Class<?>[] paramTypes,
			Annotation[][] paramAnnotations, String encoding, Map<String,String> formData, String filePath) throws Exception {
		Object[] params = new Object[paramTypes.length];
		for (int i = 0; i < paramAnnotations.length; i++) {
			if (paramAnnotations[i].length == 0) {
				params[i] = populatBean(paramTypes[i], request, encoding, formData);
			} else {
				Annotation a = paramAnnotations[i][0];
				if (a instanceof Param) {
					Param pa = (Param) a;
					String paramName = pa.value();
					String pattern = pa.pattern();
					Object v = null;
					if(formData == null) v = requestValueCast(request, paramTypes[i], paramName, pattern, encoding, null);
					else {
						for (String key : formData.keySet()) {
							if(paramName.equals(key)){
								v = requestValueCast(request, paramTypes[i], paramName, pattern, encoding, formData.get(key));
							}
						}
					}
					params[i] = v;
				}
				if(a instanceof FilePath){
					if(filePath!=null)params[i] = filePath;
				}
			}
		}
		return params;
	}
	/**
	 * 上传数据转换
	 * @param request
	 * @param type
	 * @param paramName
	 * @param encoding
	 * @param isMultipart 表单类型是否是form-data
	 * @param formName form-data中的name
	 * @param formValue form-data中的value
	 * @return
	 * @throws ParseException
	 * @throws UnsupportedEncodingException 
	 */
	private Object requestValueCast(HttpServletRequest request, Class<?> type, String paramName,String pattern,
			String encoding ,String formValue) throws ParseException, UnsupportedEncodingException {
		Object v = null;
		if(type.isArray()){
			//TODO FORM-DATA
			String[] values=request.getParameterValues(paramName);
			if(values!=null){
				Class<?> elemType=type.getComponentType();
				v=Array.newInstance(elemType, values.length);
				for (int j = 0; j < values.length; j++) {
					if(encoding!=null){
						values[j] = new String(values[j].getBytes("ISO-8859-1"),encoding);
					}
					Object elemValue=cast(elemType,values[j],pattern);
					Array.set(v, j, elemValue);
				}
			}
		}else{
			String value = null;
			if(formValue!=null) value = formValue;
			else value = request.getParameter(paramName);
			if(value!=null){
				if(encoding!=null)value = new String(value.getBytes("ISO-8859-1"),encoding);
				v = cast(type, value,pattern);
			}
		}
		return v;
	}
	/**
	 * 实体对象数据填充
	 * @param type
	 * @param request
	 * @return
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 * @throws Exception
	 */
	private Object populatBean(Class<?> type, HttpServletRequest request, String encoding, Map<String,String> formData) throws Exception {
		Object bean = type.newInstance();
		Field[] flds = type.getDeclaredFields();
		for (int i = 0; i < flds.length; i++) {
			String paramName = flds[i].getName();
			Valid va=flds[i].getAnnotation(Valid.class);
			String pattern=(va!=null)?va.pattern():null;
			Object v = null;
			if(formData == null){//默认表单内容
				v = requestValueCast(request, flds[i].getType(), paramName, pattern, encoding, null);
			}else{
				for (String key : formData.keySet()) {
					if(paramName.equals(key)){
						v = requestValueCast(request, flds[i].getType(), paramName, pattern, encoding, formData.get(key));
					}
				}
			}
			if (v != null) {
				flds[i].setAccessible(true);
				flds[i].set(bean, v);
			}
		}
		return bean;
	}
	/**
	 * 类型转换
	 * @param type
	 * @param value
	 * @return
	 * @throws ParseException
	 */
	private Object cast(Class<?> type, String value,String pattern) throws ParseException {
		String typeName = type.getSimpleName();
		if (typeName.contains("Integer") || typeName.contains("int")) {
			return Integer.parseInt(value);
		} else if (typeName.contains("Long") || typeName.contains("long")) {
			return Long.parseLong(value);
		} else if (typeName.contains("Short") || typeName.contains("short")) {
			return Short.parseShort(value);
		} else if (typeName.contains("Byte") || typeName.contains("byte")) {
			return Byte.parseByte(value);
		} else if (typeName.contains("Double") || typeName.contains("double")) {
			return Double.parseDouble(value);
		} else if (typeName.contains("Float") || typeName.contains("float")) {
			return Float.parseFloat(value);
		} else if (typeName.contains("Boolean") || typeName.contains("boolean")) {
			return Boolean.parseBoolean(value);
		} else if (typeName.contains("String")) {
			return value;
		} else if (typeName.contains("Date")) {
			SimpleDateFormat sf = new SimpleDateFormat(pattern);
			return sf.parse(value);
		} else{
			return value;
		}
	}
	/**
	 * 查找请求方法
	 * @param MethodName
	 * @param ActionClass
	 * @return
	 * @throws Exception
	 */
	private Method getRequestMethod(String MethodName, Class<?> ActionClass) throws Exception
			 {
		Method[] methods = ActionClass.getDeclaredMethods();
		Method reqMethod = null;
		for (int i = 0; i < methods.length; i++) {
			if (MethodName.equals(methods[i].getName())) {
				reqMethod = methods[i];
				break;
			}
		}
		if (reqMethod == null){
			throw new Exception("方法为空，请检查Action中是否有当前方法或检查方法名是否有错误");
		}
		return reqMethod;
	}

}
